﻿using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.IO;
using System.Linq;
using System.Text;

namespace Noria.Encode.Convert
{
    internal class Normalizer
    {
        enum FType
        {
            Ansi,
            Unicode,
            UTF8,
        }

        private string baseName;
        private string destName;
        private string[] patterns;

        public Normalizer(string targetPath, StringCollection filters)
        {
            this.patterns = filters.Cast<String>().ToArray();
            Console.WriteLine("filters: {0}", String.Join(";", patterns));

            baseName = targetPath;

            int i = 0;
            do
            {
                destName = baseName + "_" + i.ToString("00");
                i++;
            }
            while (Directory.Exists(destName));
        }

        public void Run()
        {
            List<FileInfo> files = new List<FileInfo>();

            CopyDir(baseName, destName, files);
            Console.WriteLine("{0} files totally", files.Count);

            var source = files.Where(p => patterns.Contains(p.Extension, StringComparer.OrdinalIgnoreCase));
            Console.WriteLine(" with {0} files to be converted", source.Count());
            //foreach (var f in files.Where(p => !patterns.Contains(p.Extension, StringComparer.OrdinalIgnoreCase)))
            //{
            //    Console.WriteLine("skipping ...");
            //    Console.WriteLine(" -> null [{0}]", f.FullName);
            //}

            int i = 0;
            foreach (var f in source)
            {
                //if (patterns.Contains(f.Extension, StringComparer.OrdinalIgnoreCase))
                //{
                //    Console.WriteLine("proceeding {0}...", ++i);
                //    Proceed(f);
                //}
                //else
                //{
                //    Console.WriteLine("skipping ...");
                //    Console.WriteLine(" -> null [{0}]", f.FullName);
                //}
                Console.WriteLine();
                Console.WriteLine("proceeding {0}...", ++i);
                Proceed(f);

            }
        }

        private void Proceed(FileInfo f)
        {
            try
            {
                FType type = GetFType(f);
                switch (type)
                {
                    case FType.Ansi:
                        InternalProceed(type, f, Encoding.GetEncoding("gb2312"));
                        break;
                    case FType.Unicode:
                        InternalProceed(type, f, Encoding.Unicode);
                        break;
                    case FType.UTF8:
                        InternalProceed(type, f, Encoding.UTF8);
                        break;
                }
            }
            catch (Exception e)
            {
                Console.WriteLine(e.ToString());
            }
        }

        private void InternalProceed(FType t, FileInfo f, Encoding encoding)
        {
            Console.WriteLine(" [{0}] -> {1}", t, f.FullName);

            string buffer = null;
            using (StreamReader sr = new StreamReader(f.FullName, encoding))
            {
                buffer = sr.ReadToEnd();
            }

            if (GlobalConfig.WriteBOM)
                File.WriteAllText(f.FullName, buffer, Encoding.UTF8);
            else
                File.WriteAllBytes(f.FullName, Encoding.UTF8.GetBytes(buffer));
        }

        private FType GetFType(FileInfo f)
        {
            using (StreamReader sr = new StreamReader(f.FullName, false))
            {
                if (f.Length >= 3)
                {
                    char[] buffer = new char[3];
                    if (sr.Read(buffer, 0, 3) != 3)
                    {
                        throw new Exception("Cannot read BOM");
                    }
                    if (IsUTF8(buffer))
                    {
                        //Console.WriteLine("utf-8");
                        return FType.UTF8;
                    }
                    else if (IsUTF16(buffer))
                    {
                        //Console.WriteLine("utf-16");
                        return FType.Unicode;
                    }
                    else
                    {
                        return DetectTypeWithoutBOM(f);
                    }
                }
                if (f.Length >= 2)
                {
                    char[] buffer = new char[2];
                    if (sr.Read(buffer, 0, 2) != 2)
                    {
                        throw new Exception("Cannot read BOM");
                    }
                    if (IsUTF16(buffer))
                    {
                        //Console.WriteLine("utf-16");
                        return FType.Unicode;
                    }
                    else
                    {
                        return DetectTypeWithoutBOM(f);
                    }
                }
                else
                {
                    return DetectTypeWithoutBOM(f);
                }
            }
        }

        private FType DetectTypeWithoutBOM(FileInfo f)
        {
            byte[] buffer = File.ReadAllBytes(f.FullName);
            var gb = Encoding.GetEncoding("gb2312").GetString(buffer);
            var utf8 = Encoding.UTF8.GetString(buffer);
            if (utf8.Length <= gb.Length)
            {
                //Console.WriteLine("utf-8");
                return FType.UTF8;
            }
            else
            {
                //Console.WriteLine("ansi");
                return FType.Ansi;
            }
        }

        private bool IsUTF8(char[] buffer)
        {
            return buffer[0] == 0xEF && buffer[1] == 0xBB && buffer[2] == 0xBF;
        }

        private bool IsUTF16(char[] buffer)
        {
            return buffer[0] == 0xFF && buffer[1] == 0xFE;
        }

        private void CopyDir(string baseName, string destName, List<FileInfo> files)
        {
            Directory.CreateDirectory(destName);

            foreach (var file in Directory.GetFiles(baseName))
            {
                var destFile = Path.Combine(destName, new FileInfo(file).Name);
                File.Copy(file, destFile);
                files.Add(new FileInfo(destFile));
            }

            foreach (var dir in Directory.GetDirectories(baseName))
            {
                CopyDir(dir, Path.Combine(destName, new DirectoryInfo(dir).Name), files);
            }
        }
    }
}
